/*
 * ndmp_user_if.c - The interface to user space. The IOCTLS for adding, listing and deleting NDMP sessions.
 * At the moment, it is done through IOCTL, future plan is to impliment it also through sysfs RW attributes.
 *
 *
 *      Author: Aboo Valappil
 *      Date: 2/10/2008
 *      Based on: None
 */

#include "ndmp_main.h"
#include "ndmp_ioctl.h"

extern struct ndmp_scsi_sessions sessions;

/*
 *
 * list_ndmp_session_details - populate buffer 'sp' with all the session details.
 * The same information can be obtained through sysfs
 * This routine is called from IOCTL just for the version which do not support sysfs objects
 *
 *
 */
static void list_ndmp_session_details(char *sp)
{
        struct ndmp_scsi_session *session, *tmp;
        int t=0;
        char a,b,c,d;

        list_for_each_entry_safe(session, tmp, &sessions.session_queue, list) {
                sprintf(sp+t,"\n");
                t++;
                t+=sprintf(sp+t,"Session id: %d\nNDMP Server: %s\nDevice: %s\n", session->session_id, session->ndmp_server, session->scsi_device);
                if ( session->device_type == TAPE_DEVICE ) {
                        t+=sprintf(sp+t,"Device Type: TAPE_DEVICE\n");
                } else t+=sprintf(sp+t,"Device Type: ROBOT_DEVICE\n");

               	// Get all the session status, connection status with the NDMP server, authentication status, device status and thread status
		a=ndmp_session_state(session,HBA_STATE_CONNECTION);
                b=ndmp_session_state(session,HBA_STATE_AUTH);
                c=ndmp_session_state(session,HBA_STATE_DEVICE);
                d=ndmp_session_state(session,HBA_STATE_THREAD);

		// print them accordingly to the buffer
                if ( a == _NDMP_STAT_NR ) t+=sprintf(sp+t,"Connection State: Not Ready\n");
                else  if ( a == _NDMP_STAT_IN_PROGRESS ) t+=sprintf(sp+t,"Connection State: In Progress\n");
                else  if ( a == _NDMP_STAT_FAILED ) t+=sprintf(sp+t,"Connection State: Failed\n");
                else  t+=sprintf(sp+t,"Connection State: Established\n");

                if ( b == _NDMP_STAT_NR ) t+=sprintf(sp+t,"Authentication State: Not Ready\n");
                else  if ( b == _NDMP_STAT_IN_PROGRESS ) t+=sprintf(sp+t,"Authentication State: In Progress\n");
                else  if ( b == _NDMP_STAT_FAILED ) t+=sprintf(sp+t,"Authentication State: Failed\n");
                else  t+=sprintf(sp+t,"Authentication State: Done\n");

                if ( c == _NDMP_STAT_NR ) t+=sprintf(sp+t,"Device State: Not Ready\n");
                else  if ( c == _NDMP_STAT_IN_PROGRESS ) t+=sprintf(sp+t,"Device State: In Progress\n");
                else  if ( c == _NDMP_STAT_FAILED ) t+=sprintf(sp+t,"Device State: Failed\n");
                else  t+=sprintf(sp+t,"Device State: Opened\n");

                if ( d == _NDMP_STAT_NR ) t+=sprintf(sp+t,"Thread State: Not Ready\n");
                else  if ( d == _NDMP_STAT_IN_PROGRESS ) t+=sprintf(sp+t,"Thread State: Busy\n");
                else  if ( d == _NDMP_STAT_FAILED ) t+=sprintf(sp+t,"Thread State: Failed\n");
                else  t+=sprintf(sp+t,"Thread State: idle\n");

                t+=sprintf(sp+t,"Total Number of tasks: %d\n", session->seq_number);
        }
}

/*
 *
 * ndmp_scsi_ioctl - ioctl routine to add, remove and list sessions
 * called by ioctl system call from user space on the character device (user interface device)
 *
 */
static int ndmp_scsi_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{

	// This ndmp_session is a user structure defined in ndmp_ioctl.c used to pass the session information between user and kernel
	struct ndmp_session_detail s, *ndmp_session;
	char statbuffer[2048];

	int ret=0;

	struct ndmp_scsi_session *session;

	ndmp_session = ( struct ndmp_session_detail *)arg;

	switch(cmd) {

// For creating and deleting a session
	case NDMP_SCSI_CREATE_SESSION_IOCTL: 
	case NDMP_SCSI_TERMINATE_SESSION_IOCTL:

		if ( ! access_ok(VERIFY_READ, ndmp_session, sizeof(struct ndmp_session_detail)) ) {
			printk("ndmphba: Invalid pointer from user space, user space\n");
                        ret=-EFAULT;
                        goto last;
                }

		if (__copy_from_user_inatomic(&s,ndmp_session, sizeof(struct ndmp_session_detail))) {
			printk("ndmphba: Copying session structure from user mode failed\n");
			ret=-EFAULT;
			goto last;
		}

		s.type=ROBOT_DEVICE; //Any device is ok, the ndmp_thread will figure out the correct deivce type.

		if ( cmd == NDMP_SCSI_CREATE_SESSION_IOCTL ) {

			session=ndmp_scsi_create_session(s.ndmp_server,s.user,s.passwd, s.device, s.type);

			if ( session ) {
				printk("ndmphba:%d: Created new session\n", session->session_id);
				ret=0;
				goto last;
			} else {
				printk("ndmphba:%d: New session creation failed\n", s.id);
				ret=-EFAULT;
				goto last;
			} 
		} else {

			down(&sessions.session_lock);
			printk("Session id got: %d\n", s.id);
			printk("Session id got: %s\n", s.ndmp_server);
			printk("Session id got: %s\n", s.user);
			printk("Session id got: %s\n", s.passwd);
			printk("Session id got: %s\n", s.device);
			session=get_ndmp_session(s.id);

			// the user name, device name, ndmp_server are not really important during session removal
			// but it is just put in here to make sure that user do not remove the tape drive accidentally
			// Only important information is s.id (session id). The following is just a sanity check with the existing session.
			if ( session ) {
				printk("Got session\n");
				if ( ndmp_session_state(session,HBA_STATE_THREAD) ==  _NDMP_STAT_NR ) {goto done;}
				if ( strcmp(s.ndmp_server, session->ndmp_server) ) { goto done;}
				if ( strcmp(s.user, session->username) ) { goto done;}
				if ( strcmp(s.passwd, session->password) ) { goto done;}
				if ( strcmp(s.device, session->scsi_device) ){ goto done;}
				set_bit(32, &session->state);
				ndmp_scsi_destroy_session(session);
				goto done1;
			}
		}

		done:
			printk("ndmphba: Unable to remove session: %d, session not found\n", s.id);
			ret=-ENOENT;
		done1:
			up(&sessions.session_lock);
			ret=0;
			break;

	case NDMP_SCSI_LIST_SESSION_IOCTL:

                        down(&sessions.session_lock);
			statbuffer[0]=0;
			list_ndmp_session_details(statbuffer);	
			statbuffer[2047]=0;
			up(&sessions.session_lock);
			if (__copy_to_user_inatomic((char *)arg,statbuffer, strlen(statbuffer)+1)) {
				printk("ndmphba: Copying session structure from user mode failed\n");
				ret=-EFAULT;
				goto last;
			}

			ret=0;
			
			break;
			

	default:
		ret=-EFAULT;
                break;


	}

last:
	return ret;

}

// Only ioctl is declared, rest of the operations are not defined.
static struct file_operations control_fops = {
      	.owner = THIS_MODULE,
	.ioctl = ndmp_scsi_ioctl,
};

/*
 * Add the user interface device
 *
 */
int ndmp_scsi_register_user_interface(void)
{
	int err;
	int devno;
	err=alloc_chrdev_region(&devno, 0, 1,"ndmphba");
        if ( err ) {
                printk( "ndmphba: Failed to register the control device\n");
                return 0;
        }
	cdev_init(&sessions.cdev, &control_fops);
	sessions.cdev.owner=THIS_MODULE;
	sessions.cdev.ops=&control_fops;
	err = cdev_add (&sessions.cdev,devno,1);
        if ( err ) {
                printk( "ndmphba: Failed to register the control device\n");
		unregister_chrdev_region(devno, 1);
                return 0;
        }

	kobject_set_name(&sessions.cdev.kobj, "ndmphba");

        return MAJOR(sessions.cdev.dev);
}

/*
 * remove the user interface device
 *
 */
void ndmp_scsi_unregister_user_interface(void)
{
        cdev_del(&sessions.cdev);
	unregister_chrdev_region(sessions.cdev.dev, 1);
}
